﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media.Animation;
using MahApps.Metro.Controls;
using Red.Controls.Utils;

namespace Red.Controls
{
    public enum FlyoutMode
    {
        Inwards,
        Outwards,
    }

    public class Flyout : MahApps.Metro.Controls.Flyout
    {
        #region Private Data

        private MetroWindow _parent;
        private const double MIN_HEIGHT = 15;
        private ResizeGrip _resizeGrip;
        private bool _isOpen;

        #endregion Private Data

        #region Dependency Properties

        public static readonly DependencyProperty ModeProperty =
        DependencyProperty.Register( "Mode", typeof( FlyoutMode ), typeof( Flyout ), new FrameworkPropertyMetadata( FlyoutMode.Inwards, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ModeChanged ) );

        #endregion Dependency Properties

        #region Public Properties

        public FlyoutMode Mode
        {
            get { return (FlyoutMode)GetValue( ModeProperty ); }
            set { SetValue( ModeProperty, value ); }
        }

        public bool CanResizeVertically
        {
            get { return _resizeGrip != null; }
            set
            {
                if( value )
                {
                    AddResizeGrip();
                }
                else
                {
                    RemoveResizeGrip();
                }
            }
        }

        public new double Height
        {
            get { return base.Height; }
            set
            {
                base.Height = value;

                if ( _parent != null )
                {
                    Resize();
                }
            }
        }

        #endregion Public Properties

        #region C-Tor

        public Flyout()
        {
            IsOpenChanged += Flyout_IsOpenChanged;

            Loaded += Flyout_Loaded;
        }

        #endregion C-Tor

        #region Private Methods

        private void GetAnimationVars( out DependencyProperty dp, out double parentSize, out double size )
        {
            IAnimationWrapper animationTarget = _parent as IAnimationWrapper;

            switch ( Position )
            {
                case Position.Bottom:
                    dp = HeightProperty;
                    parentSize = (double)( animationTarget?.GetActualValue( dp ) ?? _parent.Height );
                    size = Height;
                    break;

                case Position.Right:
                    dp = WidthProperty;
                    parentSize = (double)( animationTarget?.GetActualValue( dp ) ?? _parent.Width );
                    size = Width;
                    break;

                default:
                    throw new NotImplementedException( "Left/Top not supported as it would also involve moving the window" );
            }
        }

        /// <summary>
        /// Animates the size of the window using the same style of animation as metro flyouts
        /// This way the flyout will appear to enlarge the window rather than overlap
        /// </summary>
        private void Resize( double targetSize, DependencyProperty dp )
        {
            DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames();
            SplineDoubleKeyFrame frame = new SplineDoubleKeyFrame( targetSize, KeyTime.FromTimeSpan( TimeSpan.FromSeconds( 0.75 ) ), new KeySpline( 0.25,1,0.05,1 ) );
            animation.KeyFrames.Add( frame );

            IAnimationWrapper animationTarget = _parent as IAnimationWrapper;
            if ( animationTarget != null )
            {
                animationTarget.BeginAnimation( dp, animation );
            }
            else
            {
                _parent.BeginAnimation( dp, animation );
            }
        }
        
        private void AddResizeGrip()
        {
            if( _resizeGrip == null )
            {
                _resizeGrip = new ResizeGrip() { IsVerticalResizeEnabled = true };
                _resizeGrip.Resize += _resizeGrip_Resize;

                Grid root = (Grid)GetTemplateChild( "root" );

                // Will be added in the loaded event handler, otherwise
                if( root != null )
                {
                    root.Children.Add( _resizeGrip );
                }
            }
        }

        private void RemoveResizeGrip()
        {
            if( _resizeGrip != null )
            {
                _resizeGrip.Resize -= _resizeGrip_Resize;

                Grid root = (Grid)GetTemplateChild( "root" );
                root.Children.Remove( _resizeGrip );
                _resizeGrip = null;
            }
        }

        #endregion Private Methods

        #region Event Handlers

        private void _resizeGrip_Resize( double xDelta, double yDelta )
        {
            IAnimationWrapper animationTarget = _parent as IAnimationWrapper;

            if ( animationTarget != null && Height + yDelta > MIN_HEIGHT )
            {
                //Set the height of the parent window containing this flyout
                double targetHeight = (double)( animationTarget?.GetActualValue( HeightProperty ) );
                animationTarget.SetValueDirect( HeightProperty, targetHeight + yDelta );

                // Set the height of the flyout (reset any animation first as it may override us)
                BeginAnimation( HeightProperty, null );
                Height += yDelta;

                // Trigger the animation for the height we just set. (kinda like a 0 frame animation)
                // This removes a weird glitch that happens the first time the flyout is opened or closed after changing its size
                Resize( _parent.Height, HeightProperty );
            }
        }

        private void Flyout_Loaded( object sender, RoutedEventArgs e )
        {
            // The flyout internal "root" grid would not have existed before now
            if ( _resizeGrip != null )
            {
                Grid root = (Grid)GetTemplateChild( "root" );
                root.Children.Add( _resizeGrip );
            }

            MetroWindow parent = this.TryFindParent<MetroWindow>();
            if ( parent != null )
            {
                _parent = parent;

                // Without this, the first time the flyout is opened it glitches slightly
                Resize( parent.Width, WidthProperty );
                Resize( parent.Height, HeightProperty );
            }
        }

        private static void ModeChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
        {
            Flyout flyout = (Flyout)d;

            if ( flyout.IsOpen )
            {
                flyout.Resize();
            }
        }

        private void Flyout_IsOpenChanged( object sender, RoutedEventArgs e )
        {
            // A workaround for the fact that this event can be triggered erroneously by Metro if the user clicks 3+ times in quick succession (CYB-7476)
            if ( _isOpen != IsOpen )
            {
                _isOpen = IsOpen;

                if ( Mode == FlyoutMode.Outwards )
                {
                    Resize();
                }
            }
        }

        private void Resize()
        {
            DependencyProperty dp;
            double parentSize;
            double size;

            GetAnimationVars( out dp, out parentSize, out size );

            if ( IsOpen && Mode == FlyoutMode.Outwards )
            {
                Resize( parentSize + size, dp );
            }
            else
            {
                Resize( parentSize - size, dp );
            }
        }

        #endregion Event Handlers
    }

    /// <summary>
    /// Provides a behaviour to automatically allow for change of flyout mode
    /// </summary>
    public class SetFlyoutModeAction : TargetedTriggerAction<FrameworkElement>
    {
        #region Overridden Methods

        protected override void Invoke( object parameter )
        {
            Flyout flyout = (Flyout)TargetObject;
            flyout.Mode = ( flyout.Mode == FlyoutMode.Outwards ) ? FlyoutMode.Inwards : FlyoutMode.Outwards;
        }

        #endregion Overridden Methods
    }
}
